package MyDisplayObjects 
{
	import MathTextModeler.DiagramContainer;
	
	import flash.display.DisplayObjectContainer;
	import flash.display.MovieClip;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.sampler.NewObjectSample;
	import flash.text.*;
	import flash.ui.Mouse;

	/** This class creates an object that can be dragged. If the object is dragged off its parent space (any coordinate of the bounding box), the shape is automaticallly
	 * set back at the edge - i.e., it is fully contained within its parent's space.  All images should be registered (0,0) in the top-left corner. 
	 */
	public class DraggableMC extends MovieClip
	{
		public static const CENTER:int = -999;
		public static const BACK:int = -998;
		public static const FRONT:int = -997;
		public static const FONTSIZE:int = -996;
		
		public var type:String;
		public var associatedDiagramContainer:DiagramContainer;
		public var matchedPair:DraggableMC; // if this is a unit object, this object is its match
		public var associatedSiblings:Array; // if this is a unit on the variable, this is the variable object 
		public var fixed:Boolean = false; // not dragged on mouse down
		public var origin:Point;
		public var draggingBounds:Rectangle;
		// inaccessible objects
		protected var outlineShape:Shape;
		protected var tFields:Array;
		protected var unscaledWidth:Number, unscaledHeight:Number, scaledWidth:Number, scaledHeight:Number;
		protected var scaleOnMouseMultiplier:Number; // how much do we boost (or shrink) when mouse is over?
		private var scaleTranslate:Point; // how much did we move this object when scaling?
		// objects which will be accessible with getter functions
		private var _mouseOver:Boolean = false, _dragging:Boolean = false, _disabled:Boolean = false;
		// getters - restrict access to the private variables so they are read only
		public function get mouseOver():Boolean{return _mouseOver;}
		public function get dragging():Boolean{return _dragging;}
		public function get disabled():Boolean{return _disabled;}
		public function set disabled(b:Boolean):void{
			if (_disabled){ if (!b)	this.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOver);
			} else {if (b)this.removeAllListeners();
			}
			_disabled = b;
		}
		override public function get enabled():Boolean{return !_disabled;}
		override public function set enabled(b:Boolean):void{disabled = !b;}
				
		public function DraggableMC(scaleOnMouseMultiplier:Number=1)
		{
			this.scaleOnMouseMultiplier = scaleOnMouseMultiplier;
			// this event fires when the object has been added to display, this is necessary because the "parent" doesn't become active until the object has been added.
			this.addEventListener(Event.ADDED, handleAdded);
		}
		
		public function handleAdded(event:Event):void
		{
			this.removeEventListener(Event.ADDED, handleAdded);
			//this.removeEventListener(Event.ADDED, handleAdded);
			// now we have access to the parent, which is necessary to check whether this object is within the parent's space.
			if (parent != null) draggingBounds = new Rectangle(0, 0, parent.width-this.width, parent.height-this.height) 
			// add a listener for a mouse over event, which is the first step of dragging.
			if (!this.hasEventListener(MouseEvent.MOUSE_OVER))this.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOver);
		}
		/** Produces an exact new replica of this MC, should be overwritten by child classes */
		public function duplicate():DraggableMC
		{
			var dmc:DraggableMC = new DraggableMC(scaleOnMouseMultiplier); 
			dmc.name = this.name;
			dmc.type = this.type;
			return dmc;			
		}
		
		/** This object may be associated with multiple other of the same */
		public function addAssociatedSibling(dmc:DraggableMC):void
		{
			if (associatedSiblings == null) associatedSiblings = new Array();
			associatedSiblings.push(dmc);
		}
		public function get firstAssociatedSibling():DraggableMC{if (associatedSiblings != null && associatedSiblings.length > 0){return associatedSiblings[0];} else {return null;}}
		public function indexOfAssociatedSibling(dmc:DraggableMC):int
		{
			if (associatedSiblings != null) {return associatedSiblings.indexOf(dmc);}
			else {return -1;}
		}
		/** remove an associated mc from array, return index */
		public function removeAssociatedSibling(dmc:DraggableMC):int
		{
			if (associatedSiblings != null) 
			{
				var index:int = associatedSiblings.indexOf(dmc);
				if (index > -1)	{
					associatedSiblings.splice(index,1);
				} 
				return index;
			} else
			{
				return -1;
			}
		}
		public function get numAssociatedSiblings():uint{if (associatedSiblings != null){ return associatedSiblings.length;} else {return 0;}}
		public function removeAssociatedSiblings():void{
			this.associatedSiblings = new Array();
			this.removeBorder();
			if (this.matchedPair != null){
				this.matchedPair.matchedPair = null;
				this.matchedPair = null;
			}
		}
		
		/** Place a border around this object */
		public function drawBorder(thickness:Number, lineColor:uint=0xFFFFFF, lineAlpha:Number=1, shapeType:String="Ellipse"):void
		{
			if (thickness <= 0)
			{ // remove border
				if (outlineShape != null && outlineShape.parent == this) this.removeChild(outlineShape);
			} else
			{ // add border
				if (outlineShape == null || outlineShape.parent != this)
				{
					outlineShape = new Shape();
					this.addChild(outlineShape);
					outlineShape.graphics.lineStyle(thickness, lineColor, lineAlpha);
					if (shapeType.toLowerCase() == "rect" || shapeType.toLowerCase() == "rectangle")
					{
						outlineShape.graphics.drawRect(-thickness, -thickness, this.width+2*thickness, this.height+2*thickness);
					} else if (shapeType.toLowerCase() == "ellipse")
					{
						outlineShape.graphics.drawEllipse(-thickness, -thickness, this.width+2*thickness, this.height+2*thickness);
					} else if (shapeType.toLowerCase() == "circle")
					{
						outlineShape.graphics.drawCircle(this.width/2, this.height/2, Math.max(this.width/2+thickness, this.height/2+thickness));
					} 
				}
			}
		}
		/** Remove the outline used to match 
		 */ 
		public function removeBorder ():void{
			if (this.outlineShape != null){
				if (this.outlineShape.parent == this) this.removeChild(outlineShape);
				this.outlineShape = null;
			}
		}
		/** Add a (noselectable) textfield at the given position, position should be relative to this mc's space */
		public function addTextLabel (text:String, fontSize:Number, fontColor:uint, x:Number=CENTER, y:Number=FONTSIZE, offsetX:Number=0, offsetY:Number=0):void
		{
			if (tFields == null) tFields = new Array();
			var tField:TextField;
			tField = new TextField();
			tField.selectable = false;
			tField.mouseEnabled = false;
			tField.defaultTextFormat = new TextFormat("Arial", fontSize, fontColor);
			tField.text = text;
			tField.autoSize = "left";
			this.addChild(tField);
			tFields.push(tField);
			if (x == CENTER)
			{
				tField.x = (this.width - tField.width)/2;
			} else if (x == BACK)
			{
				tField.x = (this.width - tField.width - offsetX);
			}  else if (x == FRONT)
			{
				tField.x = offsetX;
			} else
			{
				tField.x = x;
			}
			if (y == CENTER)
			{
				tField.y = (this.height - tField.height)/2;
			} else if (y == BACK)
			{
				tField.y = (this.height - tField.height - offsetY);
			} else if (y == FRONT)
			{
				tField.y = (this.height - tField.height - offsetX);
			} else if (y == FONTSIZE){
				tField.y = -fontSize;
			} else
			{
				tField.y = y;
			}
		}
		public function findTextLabelWith (s:String):int
		{
			for (var i:uint=0; i < tFields.length; i++)
			{
				var tField:TextField = tFields[i];
				if (tField.text == s) return i;
			}
			return i;
		}
		public function removeTextLabelAt (index:int):String
		{
			var tField:TextField = tFields.splice(index, 1);
			return (tField.text);
		}
		////////////////////////////////  PLACEMENT MOVEMENT //////////////////////////////////////////////////////////////
		/** Return this object to its original location */
		public function returnToOrigin():Boolean
		{
			if (origin != null)
			{
				this.x = origin.x; this.y = origin.y; return true;
			} else {
				return false;
			}
		}
		
		/** Will move to a place in a square with the following cardinality, 1, 9, 25, 49.
		 * 0 is center, 1 is top-left of first square (ring), moves clockwise, at 25 moves to next ring.
		 */
		public function moveSquareFromPoint(point:Point, index:uint, distanceMultiplier:Number=1, scale:Number=1, offsetX:Number=-1, offsetY:Number=-1):void
		{
			//trace(this.width, this.height, point, index, distanceMultiplier, scale);
			if (offsetX < 0 || offsetY < 0)
			{
				offsetX = this.width * distanceMultiplier * scale;
				offsetY = this.height * distanceMultiplier * scale;
			}
			this.x = point.x - this.width/2;//*scale;
			this.y = point.y - this.height/2;//*scale;
			switch (index+1)
			{
				case 1: this.x +=  0*offsetX; this.y +=  0*offsetY; break;
				case 2: this.x += -1*offsetX; this.y += -1*offsetY; break; 
				case 3: this.x +=  0*offsetX; this.y += -1*offsetY; break; 
				case 4: this.x +=  1*offsetX; this.y += -1*offsetY; break; 
				case 5: this.x +=  1*offsetX; this.y +=  0*offsetY; break; 
				case 6: this.x +=  1*offsetX; this.y +=  1*offsetY; break; 
				case 7: this.x +=  0*offsetX; this.y +=  1*offsetY; break; 
				case 8: this.x += -1*offsetX; this.y +=  1*offsetY; break; 
				case 9: this.x += -1*offsetX; this.y +=  0*offsetY; break; 
				case 10: this.x += -2*offsetX; this.y += -2*offsetY; break; 
				case 11: this.x += -1*offsetX; this.y += -2*offsetY; break; 
				case 12: this.x +=  0*offsetX; this.y += -2*offsetY; break; 
				case 13: this.x +=  1*offsetX; this.y += -2*offsetY; break; 
				case 14: this.x +=  2*offsetX; this.y += -2*offsetY; break; 
				case 15: this.x +=  2*offsetX; this.y += -1*offsetY; break; 
				case 16: this.x +=  2*offsetX; this.y +=  0*offsetY; break; 
				case 17: this.x +=  2*offsetX; this.y +=  1*offsetY; break; 
				case 18: this.x +=  2*offsetX; this.y +=  2*offsetY; break; 
				case 19: this.x +=  1*offsetX; this.y +=  2*offsetY; break; 
				case 20: this.x +=  0*offsetX; this.y +=  2*offsetY; break; 
				case 21: this.x += -1*offsetX; this.y +=  2*offsetY; break; 
				case 22: this.x += -2*offsetX; this.y +=  2*offsetY; break; 
				case 23: this.x += -2*offsetX; this.y +=  1*offsetY; break; 
				case 24: this.x += -2*offsetX; this.y +=  0*offsetY; break; 
				case 25: this.x += -2*offsetX; this.y += -1*offsetY; break; 
				default: this.x += offsetX*(4*Math.random() - 2); this.y += offsetY*(4*Math.random() - 2); trace("here");break;
			} 
		}
		///////////////////////////////		MOUSE INTERACTION FUNCTIONS ///////////////////////////////////////////////////
		/** Fires when user is over this object */
		private function handleMouseOver (event:MouseEvent):void
		{
			// remove the event listener so it doesn't keep firing and calling this function repeatedly
			this.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOver);
			_mouseOver = true; // set a variable so that we can know, from outside of this object, whether we are currently over this draggable mc.
			
			if (scaleOnMouseMultiplier != 1 && !dragging)
			{
				/*
				var point:Point = new Point(this.mouseX, this.mouseY);
				unscaledWidth = this.width;
				unscaledHeight = this.height;
				this.scaleX = scaleOnMouseMultiplier;
				this.scaleY = scaleOnMouseMultiplier;
				scaledWidth = this.width;
				scaledHeight = this.height;
				this.graphics.beginFill(0xFFFFFF, .1);
				this.graphics.drawRect(0, 0, this.width, this.height);
				this.graphics.endFill();
				*/
				// we need to translate this image a bit so that the pixel underneath the mouse remains the same
				//scaleTranslate = new Point();
				//scaleTranslate.x = point.x*scaleOnMouseMultiplier - point.x;
				//scaleTranslate.y = point.y*scaleOnMouseMultiplier - point.y;
				//this.x -= scaleTranslate.x;
				//this.y -= scaleTranslate.y;
			}
			
			// add new event listeners, responding to a mouse down (start of drag)
			this.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
			// add a mouse out listener in case user is simply passing over this object incidentily
			this.addEventListener(MouseEvent.MOUSE_OUT, handleMouseOut);
		}
		
		/** Fires when user presses down on this object.  Should only be called when user is over this object */
		private function handleMouseDown (event:MouseEvent):void
		{
			// remove down listener
			this.removeEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
			// since this object should be moving underneath our mouse, the mouse out event doesn't make sense to keep active
			this.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOut);
			
			if (!fixed && !_disabled)
			{
				_dragging = true; // we are dragging now
				this.startDrag(false, draggingBounds);
				this.play();
				Mouse.hide();
			}
			// add a mouse up event to stage, adding to the stage is important in case the mouse is somewhere else than over this object when the user lifts his or her finger.
			if (this.stage != null)
			{
				this.stage.addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);			
			} else 
			{
				this.addEventListener(MouseEvent.MOUSE_OUT, handleMouseOut);
				this.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
			}
		}
		
		/** Fires when user releases the mouse anywhere over the stage.  Resets all variables */
		private function handleMouseUp (event:MouseEvent):void
		{
			if (!fixed && !_disabled)
			{
				_dragging = false;
				this.stopDrag();
				this.stop();
				Mouse.show();
			}
			if (this.stage == null) return;
				
			this.stage.removeEventListener(MouseEvent.MOUSE_UP, handleMouseUp);	//obviously, see above for reason why	
			// get global coordinates of this mouse point - confusing stuff
			var gpoint:Point = this.localToGlobal(new Point(this.mouseX, this.mouseY));
			
			// Is the mouse still over this object?
			if (this.hitTestPoint(gpoint.x, gpoint.y))
			{
				// yes, add a mouse out listener and mouse down listener
				this.addEventListener(MouseEvent.MOUSE_OUT, handleMouseOut);
				this.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
				// _mouseOver should still be true.
			} else
			{
				// no, adjust variable, add a mouse over listener
				_mouseOver = false;
				this.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOver);
			}
		}
		/** Fires when mouse leaves the space of this object, this should not be active when we are dragging */
		private function handleMouseOut (event:MouseEvent):void
		{
			// remove out and down listeners, place over listener
			this.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOut);
			this.removeEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
			_mouseOver = false;
			
			if (scaleOnMouseMultiplier != 1 && !dragging)
			{
				//this.width = unscaledWidth;
				//this.height = unscaledHeight;
				//this.x += scaleTranslate.x;
				//this.y += scaleTranslate.y;
				//this.graphics.clear();
			}
			
			this.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOver);			
		}
			
		
		/** Removes listeners for disabling */
		private function removeAllListeners ():void
		{
			if (this.hasEventListener(MouseEvent.MOUSE_OVER)) this.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOver);
			if (this.hasEventListener(MouseEvent.MOUSE_OUT)) this.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOut);
			if (this.hasEventListener(MouseEvent.MOUSE_DOWN)) this.removeEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
			if (this.hasEventListener(MouseEvent.MOUSE_UP)) this.removeEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
			Mouse.show();
		}
		
		
	}
}